use fs4::fs_std::FileExt;
use log::error;
use std::{
    error::Error,
    fs::{create_dir, File, OpenOptions},
    io::{BufReader, BufWriter, Read, Seek, Write},
    path::PathBuf,
    result::Result,
};

#[derive(Debug)]
pub struct Log {
    pub path: PathBuf,
    pub file: File,
}

impl Log {
    pub fn new(path_str: &str, name: &str) -> Self {
        let mut path = PathBuf::from(path_str);
        if !path.exists() {
            if let Err(e) = create_dir(path.as_path()) {
                panic!("create dir error, {}", e);
            }
        }
        path.push(name);
        let file = match OpenOptions::new()
            .create(true)
            .read(true)
            .write(true)
            .open(path.clone())
        {
            Ok(f) => f,
            Err(e) => {
                panic!("file {}/{} log open failure, {}", path_str, name, e);
            }
        };
        if let Err(e) = file.lock_exclusive() {
            error!("file {}/{} locked failure! {}", path_str, name, e);
        }

        Log { path, file }
    }

    pub fn read(&self, pos: u64) -> Result<(Vec<u8>, Option<Vec<u8>>), Box<dyn Error>> {
        let mut buf_reader = BufReader::new(&self.file);
        buf_reader.seek(std::io::SeekFrom::Start(pos))?;
        let mut key_len_buf = vec![0_u8; 4];
        buf_reader.read_exact(&mut key_len_buf)?;
        let mut value_len_buf = vec![0_u8; 4];
        buf_reader.read_exact(&mut value_len_buf)?;

        let key_len: u32 = bincode::deserialize(&key_len_buf).unwrap();
        let value_len: i32 = bincode::deserialize(&value_len_buf).unwrap();

        let mut key_buf = vec![0_u8; key_len as usize];
        buf_reader.read_exact(&mut key_buf)?;

        if -1 == value_len {
            return Ok((key_buf, None));
        }

        let mut value_buf = vec![0_u8; value_len as usize];
        buf_reader.read_exact(&mut value_buf)?;

        Ok((key_buf, Some(value_buf)))
    }

    pub fn write(&self, key: &Vec<u8>, value: &Vec<u8>) -> Result<u64, Box<dyn Error>> {
        let key_len = key.len() as u32;
        let value_len = value.len();
        let tombstone_value_len = match value_len {
            0 => -1_i32,
            i => i as i32,
        };

        let mut buf_writer = BufWriter::new(&self.file);
        let pos = buf_writer.seek(std::io::SeekFrom::End(0))?;
        buf_writer.write_all(&bincode::serialize(&key_len).unwrap())?;
        buf_writer.write_all(&bincode::serialize(&tombstone_value_len).unwrap())?;
        buf_writer.write_all(key)?;
        if -1 != tombstone_value_len {
            buf_writer.write_all(value)?;
        }

        Ok(pos)
    }
}

#[cfg(test)]
mod test {

    use std::error::Error;

    use super::Log;

    #[test]
    fn test() -> Result<(), Box<dyn Error>> {
        let path = "/tmp/rust-db/";
        let log = Log::new(path, "db.log");
        let _pos1 = log.write(
            &bincode::serialize("hello").unwrap(),
            &bincode::serialize("world").unwrap(),
        )?;
        let _pos2 = log.write(
            &bincode::serialize("hi").unwrap(),
            &bincode::serialize("rust").unwrap(),
        )?;
        let _pos3 = log.write(
            &bincode::serialize("hi2").unwrap(),
            &bincode::serialize("rust2").unwrap(),
        )?;
        let pos4 = log.write(
            &bincode::serialize("hi3").unwrap(),
            &bincode::serialize("rust3").unwrap(),
        )?;

        let (key, value) = log.read(pos4)?;
        println!(
            "key = {:#?}, value = {:#?}",
            bincode::deserialize::<String>(&key),
            bincode::deserialize::<String>(&value.unwrap())
        );

        Ok(())
    }
}
